
package client;

import com.jme3.app.Application;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.util.TangentBinormalGenerator;
import java.util.concurrent.Callable;

/**
 *
 * @author xissburg
 */
public class GraphicsWorld extends Application
{
    private Node rootNode;
    private Material buttonMat;
    private Material ballMat;
    private Geometry fieldShape;
    //private Geometry buttonPosShape;

    //Each bit in this bitset represents a spectator slot. True means it is
    //in use, false means it is available.
    //private BitSet spectatorSlots;
    //private final int maxSpectators;

    //Maps button/client name to ClientInfo with the button/client state
    //private Map<String, ClientInfo> buttonsMap;
    //private Vector3f spectatorStartPosition;
    //private Vector3f spectatorDisplacement;

    final float buttonsY = 0.1f;

    GraphicsWorld() {
        super();
        rootNode = new Node("root");
    }

    @Override
    public void initialize()
    {
        super.initialize();
        viewPort.attachScene(rootNode);

        //Create basic objects and materials
        initializeObjects();
    }

    private void initializeObjects()
    {
        cam.setLocation(new Vector3f(0f, 17f, 4f));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);

        //field material
        Material fieldMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        fieldMat.setFloat("m_Shininess", 2);
        fieldMat.setBoolean("m_UseMaterialColors", true);
        fieldMat.setColor("m_Diffuse", new ColorRGBA(0.2f, 0.7f, 0.2f, 1f));
        fieldMat.setColor("m_Ambient",  ColorRGBA.Black);
        fieldMat.setColor("m_Specular", ColorRGBA.DarkGray);

        //field box
        final float fieldHeight = 0.2f;
        Box fieldBox = new Box(new Vector3f(0f, -fieldHeight/2, 0f), 9.2f, fieldHeight, 6.7f);
        TangentBinormalGenerator.generate(fieldBox);
        fieldShape = new Geometry("field", fieldBox);
        fieldShape.setMaterial(fieldMat);
        rootNode.attachChild(fieldShape);

        //default material
        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat.setFloat("m_Shininess", 3);
        mat.setBoolean("m_UseMaterialColors", true);
        mat.setColor("m_Diffuse", new ColorRGBA(0.6f, 0.6f, 0.7f, 1f));
        mat.setColor("m_Ambient",  ColorRGBA.Black);
        mat.setColor("m_Specular", ColorRGBA.Gray);

        final float wallHeight = 1f;
        //top wall
        Box wallBox = new Box(new Vector3f(0f, wallHeight/2, 6.2f), 9.2f, wallHeight, 1.f);
        TangentBinormalGenerator.generate(wallBox);
        Geometry wallGeom = new Geometry("topWall", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        //bottom wall
        wallBox = new Box(new Vector3f(0f, wallHeight/2, -6.2f), 9.2f, wallHeight, 1.f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("bottomWall", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        //left wall
        wallBox = new Box(new Vector3f(-10f, wallHeight/2, 0f), 1f, wallHeight, 7.2f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("leftWall", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        wallBox = new Box(new Vector3f(-8.7f, wallHeight/2, 3.1f), 0.3f, wallHeight, 2.1f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("leftWall1", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        wallBox = new Box(new Vector3f(-8.7f, wallHeight/2, -3.1f), 0.3f, wallHeight, 2.1f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("leftWall2", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        //right wall
        wallBox = new Box(new Vector3f(10f, wallHeight/2, 0f), 1f, wallHeight, 7.2f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("rightWall", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        wallBox = new Box(new Vector3f(8.7f, wallHeight/2, 3.1f), 0.3f, wallHeight, 2.1f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("rightWall1", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        wallBox = new Box(new Vector3f(8.7f, wallHeight/2, -3.1f), 0.3f, wallHeight, 2.1f);
        TangentBinormalGenerator.generate(wallBox);
        wallGeom = new Geometry("rightWall2", wallBox);
        wallGeom.setMaterial(mat);
        rootNode.attachChild(wallGeom);

        //button material
        buttonMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        buttonMat.setFloat("m_Shininess", 64);
        buttonMat.setBoolean("m_UseMaterialColors", true);
        buttonMat.setColor("m_Diffuse", new ColorRGBA(0.1f, 0.1f, 0.1f, 1f));
        buttonMat.setColor("m_Ambient",  ColorRGBA.Black);
        buttonMat.setColor("m_Specular", ColorRGBA.LightGray);

        //ball material
        ballMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        ballMat.setFloat("m_Shininess", 64);
        ballMat.setBoolean("m_UseMaterialColors", true);
        ballMat.setColor("m_Diffuse", new ColorRGBA(0.8f, 0.5f, 0.1f, 1f));
        ballMat.setColor("m_Ambient",  ColorRGBA.Black);
        ballMat.setColor("m_Specular", ColorRGBA.LightGray);

        //Create and add ball
        Cylinder ballCyl = new Cylinder(16, 8, 0.1f, 0.1f, true);
        TangentBinormalGenerator.generate(ballCyl);
        Geometry ballShape = new Geometry("$ball", ballCyl);
        ballShape.setMaterial(ballMat);
        ballShape.setLocalRotation(new Quaternion().fromAngleNormalAxis((float)Math.PI/2, Vector3f.UNIT_X));
        ballShape.setLocalTranslation(new Vector3f(0f, 0.1f, 0f));

        rootNode.attachChild(ballShape);

        

        //button positioner material
        /*Material buttonPosMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        buttonPosMat.setFloat("m_Shininess", 64);
        buttonPosMat.setBoolean("m_UseMaterialColors", true);
        buttonPosMat.setColor("m_Diffuse", new ColorRGBA(0.2f, 0.8f, 0.8f, 1f));
        buttonPosMat.setColor("m_Ambient",  ColorRGBA.Black);
        buttonPosMat.setColor("m_Specular", ColorRGBA.LightGray);*/

        //button positioner shape
        /*Cylinder buttonPosCyl = new Cylinder(32, 16, 0.3f, 0.1f, true);
        TangentBinormalGenerator.generate(buttonPosCyl);
        buttonPosShape = new Geometry("buttonPositioner", buttonPosCyl);
        buttonPosShape.setMaterial(buttonPosMat);
        buttonPosShape.setLocalRotation(new Quaternion().fromAngleNormalAxis((float)Math.PI/2, Vector3f.UNIT_X));
        buttonPosShape.setLocalTranslation(new Vector3f(0f, 0.1f, 0f));
        rootNode.attachChild(buttonPosShape);*/
        

        PointLight pl = new PointLight();
        pl.setPosition(new Vector3f(2f, 10f, 2f));
        pl.setColor(ColorRGBA.White.clone());
        pl.setRadius(200f);
        rootNode.addLight(pl);
    }

    @Override
    public void update()
    {
        super.update();

        float tpf = timer.getTimePerFrame() * speed;

        // update states
        stateManager.update(tpf);

         // simple update and root node
        rootNode.updateLogicalState(tpf);
        rootNode.updateGeometricState();

        // render states
        stateManager.render(renderManager);
        renderManager.render(tpf);
    }

    /**
     * Creates and adds a new button for a given client.
     *
     * @param clientInfo The client data to create the button for. This data is
     * used to configure some stuff for the new button.
     */
    public void addButton(final String name, final float x, final float y)
    {
        //run in OpenGL thread
        enqueue(new Callable<Void>() {
            public Void call() throws Exception {
                //If there's already a node with the same name, remove it first
                rootNode.detachChildNamed(name);
                    
                //create button shape
                Cylinder buttonCyl = new Cylinder(32, 16, 0.3f, 0.1f, true);
                TangentBinormalGenerator.generate(buttonCyl);
                final Geometry buttonShape = new Geometry(name, buttonCyl);
                buttonShape.setMaterial(buttonMat);
                buttonShape.setLocalRotation(new Quaternion().fromAngleNormalAxis((float)Math.PI/2, Vector3f.UNIT_X));
                buttonShape.setLocalTranslation(new Vector3f(x,buttonsY,y));

                rootNode.attachChild(buttonShape);

                return null;
            }
        });
    }

    public void removeNode(final String name)
    {
        enqueue(new Callable<Void>() {
            public Void call() {
                rootNode.detachChildNamed(name);
                return null;
            }
        });
    }

    public void setButtonPosition(final String name, final float x, final float y)
    {
        enqueue(new Callable<Void>() {
                public Void call() throws Exception {
                    Spatial child = rootNode.getChild(name);
                    if (child != null)
                        child.setLocalTranslation(new Vector3f(x, buttonsY, y));
                    return null;
                }
            });
    }

    public void setNodePosition(final String name, final Vector3f position, boolean openGLThread)
    {
        if(openGLThread)
        {
            Spatial child = rootNode.getChild(name);
            if (child != null)
                child.setLocalTranslation(position);
        }
        else
        {
            enqueue(new Callable<Void>() {
                public Void call() throws Exception {
                    Spatial child = rootNode.getChild(name);
                    if (child != null)
                        child.setLocalTranslation(position);
                    return null;
                }
            });
        }
    }

    public Vector3f screenRayCastField(Vector2f cursorPos)
    {
        CollisionResults cResults = new CollisionResults();
        Vector3f pos = cam.getWorldCoordinates(cursorPos, 0);
        Vector3f dir = cam.getWorldCoordinates(cursorPos, 1f);
        dir.subtractLocal(pos).normalizeLocal();
        Ray ray = new Ray(pos, dir);
        fieldShape.collideWith(ray, cResults);
        CollisionResult result = cResults.getClosestCollision();

        if(result == null)
            return null;
        
        return result.getContactPoint();
    }


    /**
     * Stores data about a spectator slot. It has the position of the slot and the
     * name of the button which is currently using it. The name is null if it is
     * available for use by another button.
     */
    private static class SpectatorSlot
    {
        private Vector3f position;
        private String name;

        public SpectatorSlot(Vector3f position, String name) {
            this.position = position;
            this.name = name;
        }

        public SpectatorSlot(Vector3f position) {
            this.position = position;
            name = null;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Vector3f getPosition() {
            return position;
        }

        public void setPosition(Vector3f position) {
            this.position = position;
        }
    }
}
